#include "pr/NearestMeanClassifier.h"
#include "pr/Pattern.h"
#include "util/Rank.h"
#include "util/Log.h"
#include "util/Debug.h"
#include <iostream>
#include <stdexcept>

namespace pr {

NearestMeanClassifier::NearestMeanClassifier(std::size_t numClasses, std::size_t numFeatures)
  : numClasses_(numClasses)
  , numFeatures_(numFeatures)
  , classes_(numClasses, Category(Pattern(numFeatures), 0))
{
}

const Pattern& NearestMeanClassifier::mean(std::size_t classId)
{
  return classes_[classId].mean();
}

std::size_t NearestMeanClassifier::patternCount(std::size_t classlabel)
{
  return classes_[classlabel].patternCount();
}

void NearestMeanClassifier::train(const Pattern& trainingPattern, std::size_t classId)
{
  update(trainingPattern, classId);
}

std::size_t NearestMeanClassifier::classify(const Pattern& testPattern)
{
  std::size_t result = 0;
  util::Real i = 0.0;

  const_iterator it = classes_.begin();

//  DEBUG_EXEC(
//  {
//    LOG("NearestMeanClassifier", "classify", "Test pattern: ");
//    util::Formatter::getInstance().toStream(testPattern, std::cout);
//  }
//  )
  util::Real distance = it->mean().euclideanDistance(testPattern) * it->emissionProbability();

  ++it;
  for ( ; it != classes_.end(); ++it)
  {
    if ((i = it->mean().euclideanDistance(testPattern) * it->emissionProbability()) < distance)
    {
      result = it - classes_.begin();
      distance = i;
    }
  }

  return result;
}

bool NearestMeanClassifier::test(const Pattern& testPattern, std::size_t classId)
{
  return classify(testPattern) == classId;
}

bool NearestMeanClassifier::rank(const Pattern& testPattern, std::size_t classId, std::size_t rank)
{
  if (rank >= numClasses_)
    return true;

  util::Rank r(numClasses_, rank);
  for (std::size_t j = 0; j < numClasses_; ++j)
    r.addElement(j, testPattern.euclideanDistance(classes_[j].mean()));

  return r.result(classId);
}

bool NearestMeanClassifier::classifyAndTrain(const Pattern& testPattern, std::size_t classId)
{
  std::size_t computedClassId = classify(testPattern);
  update(testPattern, classId);
  return computedClassId == classId;
}

//-------------------------------
//bool nmc::verify(pvector& pv, unsigned int cl, util::Real th)
//{
//  return (pv.euclideandist(means[cl]) < th );
//}
//-------------------------------
//void nmc::distvector(pvector& pv, pvector& dist)
//{
//  unsigned int  h;
//
//  for (h=0; h<nclasses(); h++)
//    dist[h] = means[h].euclideandist(pv)*classemissionprob[h];
//}

// private
void NearestMeanClassifier::update(const Pattern& trainingPattern, std::size_t classId)
{
  if (classId >= classes_.size())
    throw std::invalid_argument("classId does not exist.");
  classes_[classId].addPattern(trainingPattern);
}

void NearestMeanClassifier::Category::addPattern(const Pattern& trainingPattern)
{
  ++patternCount_;
  meanPattern_ = (patternCount_ == 1) ?
    trainingPattern :
    ((meanPattern_ * (patternCount_ - 1)) + trainingPattern) / patternCount_;
}

} // namespace pr
